package com.foreveross.mofang.serviceconsole.web.controller.common;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.HandlerMethodSelector;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.ServletRequestDataBinderFactory;
import org.springframework.web.servlet.support.RequestContextUtils;

/**
 * 对外接口调用 日志记录拦截器类
 * 
 * @author fb
 */
public class InterfaceLog implements HandlerInterceptor {

	private final static Logger log = LoggerFactory.getLogger("interface");
	  
	private final static Logger logger = LoggerFactory.getLogger(InterfaceLog.class);
	
	private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
			new ConcurrentHashMap<MethodParameter, HandlerMethodArgumentResolver>();
	
	private final Map<Class<?>, Set<Method>> initBinderCache = new ConcurrentHashMap<Class<?>, Set<Method>>(64);
	
	private Map<String, Object> params = new HashMap<String, Object>();
	
	private List<HandlerMethodArgumentResolver> argumentResolvers;
	
	@Inject
	private RequestMappingHandlerAdapter adapter;
	
	@Inject
	public InterfaceLog(RequestMappingHandlerAdapter requestMappingHandlerAdapter){
		argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers().getResolvers();
	}
			
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object obj, Exception exception) {
		HandlerMethod handlerMethod = (HandlerMethod)obj;
		LogAn logAn = handlerMethod.getMethodAnnotation(LogAn.class);
		String ip = getRemoteIp(request);
		int port = request.getRemotePort();
		String name = logAn.name();
		StringBuilder sb = new StringBuilder();
		String[] fieldNams = null;
		
		try {
			paramsInit(handlerMethod, request, response);

			// 如果为 user.name，则表示获取user对象中name值
			for (String paramName : logAn.params()) {
				fieldNams = paramName.split("\\.");
				String pName = fieldNams[0];
				Object value = params.get(pName);

				if (fieldNams.length > 1) {
					for (int i = 1; i < fieldNams.length; i++) {
						pName += "." + fieldNams[i];
						value = value.getClass().getDeclaredField(fieldNams[i])
								.get(value);
					}
				}

				sb.append(pName).append(":").append(value).append("  ");
			}
			
			if (sb.length() == 0) {
				sb.append("无");
			}
	 
			log.info("{} | {} | {} | {}", new Object[]{ip, port, name, sb.toString()});

		} catch (Exception e) {
			logger.error("接口[{}]日志记录异常",handlerMethod.getMethod().getName(), e);
		}
		
	}

	public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1,
			Object arg2, ModelAndView arg3) throws Exception {
		
	}

	public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1,
			Object arg2) throws Exception {

		return true;
	}
	
	private void paramsInit(HandlerMethod handlerMethod,HttpServletRequest request, HttpServletResponse response) throws Exception {
		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		MethodParameter[] parameters = handlerMethod.getMethodParameters();
		
		for (int i = 0; i < parameters.length; i++) {
			MethodParameter parameter = parameters[i];
			HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
			ModelAndViewContainer mavContainer = new ModelAndViewContainer();
			mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
			WebDataBinderFactory webDataBinderFactory = getDataBinderFactory(handlerMethod);

			params.put(parameter.getParameterName(),
					resolver.resolveArgument(parameter, mavContainer, webRequest, webDataBinderFactory));
		}
	}

	private String getRemoteIp(HttpServletRequest request) {
		String ip = request.getHeader("X-Real-IP");
		
		if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("X-Forwarded-For");
		} else {
			return ip;
		}
		
		if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getRemoteAddr();
		} else {
			int index = ip.indexOf(',');
			
			if (index != -1) {
				ip = ip.substring(0, index);
			}
		}
		
		return ip;
	}
	
	private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
		HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
		if (result == null) {
			for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
				if (methodArgumentResolver.supportsParameter(parameter)) {
					result = methodArgumentResolver;
					this.argumentResolverCache.put(parameter, result);
					break;
				}
			}
		}
		return result;
	}
	
	private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
		Class<?> handlerType = handlerMethod.getBeanType();
		Set<Method> methods = this.initBinderCache.get(handlerType);
		
		if (methods == null) {
			methods = HandlerMethodSelector.selectMethods(handlerType, RequestMappingHandlerAdapter.INIT_BINDER_METHODS);
			this.initBinderCache.put(handlerType, methods);
		}
		
		List<InvocableHandlerMethod> initBinderMethods = new ArrayList<InvocableHandlerMethod>();
		
		for (Method method : methods) {
			Object bean = handlerMethod.getBean();
			initBinderMethods.add(new InvocableHandlerMethod(bean, method));
		}
		
		return new ServletRequestDataBinderFactory(initBinderMethods, adapter.getWebBindingInitializer());
	}

}
